using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace SceneEngine
{
    public class Camera : ICamera
    {
        private Vector3 position;
        private Matrix projection;

        private Vector3 target;
        private Vector3 forward;

        private float yaw;
        private float pitch;

        private float speed;

        private MouseState mouseState;
        private MouseState prevMouseState;

        /// <summary>
        /// Get and set the camera's position.
        /// </summary>
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }
        /// <summary>
        /// Returns the camera's forward vector.
        /// </summary>
        public Vector3 Forward
        {
            get { return forward; }
        }

        /// <summary>
        /// Defines how fast the camera moves.
        /// </summary>
        public float Speed
        {
            get { return speed; }
            set { speed = value; }
        }

        /// <summary>
        /// Returns the camera's rotation around the y-axis
        /// </summary>
        public float Yaw
        {
            get { return yaw; }
        }
        /// <summary>
        /// Returns the camera's rotation around the x-axis
        /// </summary>
        public float Pitch
        {
            get { return pitch; }
        }

        /// <summary>
        /// Returns the camera's view matrix.
        /// </summary>
        public Matrix View
        {
            get { return Matrix.CreateLookAt(position, target, Vector3.Up); }
        }
        /// <summary>
        /// Returns the camera's projection matrix.
        /// </summary>
        public Matrix Projection
        {
            get { return projection; }
        }

        /// <summary>
        /// The camera's view frustum.
        /// </summary>
        public BoundingFrustum Frustum
        {
            get { return new BoundingFrustum(View * Projection); }
        }

        public float NearPlane
        {
            get { return 1.0f; }
        }
        public float FarPlane
        {
            get { return 10000.0f; }
        }

        /// <summary>
        /// Constructs a new Camera object.
        /// </summary>
        public Camera(Vector3 position, float speed) 
        {
            this.position = position;
            this.speed = speed;

            target = Vector3.Forward + position;

            UpdateProjection();

            Vector3 direction = Vector3.Normalize(-position);
            ChangeDirection(direction);
        }

        /// <summary>
        /// Points the camera in the specified direction.
        /// </summary>
        public void ChangeDirection(Vector3 direction)
        {
            pitch = (float)Math.Asin(direction.Y);

            yaw = (float)Math.Atan2((double)-direction.X, 
                                    (double)-direction.Z);

            Matrix cameraViewRotationMatrix = Matrix.CreateRotationX(pitch) *
                                              Matrix.CreateRotationY(yaw);

            Vector3 transformedCameraReference = Vector3.Transform(
                Vector3.Forward, cameraViewRotationMatrix);

            forward = transformedCameraReference;
            target = forward + position;
        }

        /// <summary>
        /// Updates the camera based on user input.
        /// </summary>
        public void Update(float timeDelta)
        {
            KeyboardState keyState = Keyboard.GetState();
            mouseState = Mouse.GetState();

            Vector3 moveVector = Vector3.Zero;

            List<Keys> pressedKeys = new List<Keys>();
            pressedKeys.AddRange(keyState.GetPressedKeys());

            if (!pressedKeys.Contains(Keys.LeftControl) &&
                !pressedKeys.Contains(Keys.LeftShift))
            {
                if (keyState.IsKeyDown(Keys.W))
                    moveVector.Y += speed * timeDelta;

                if (keyState.IsKeyDown(Keys.S))
                    moveVector.Y -= speed * timeDelta;

                if (keyState.IsKeyDown(Keys.A))
                    moveVector.X -= speed * timeDelta;

                if (keyState.IsKeyDown(Keys.D))
                    moveVector.X += speed * timeDelta;
            }

            if (mouseState.RightButton == ButtonState.Pressed)
            {
                float deltaX = mouseState.X - prevMouseState.X;
                float deltaY = mouseState.Y - prevMouseState.Y;

                yaw -= (deltaX * 0.4f) * timeDelta;

                pitch -= (deltaY * 0.4f) * timeDelta;
                pitch = MathHelper.Clamp(pitch, MathHelper.ToRadians(-89.9f), MathHelper.ToRadians(89.9f));
            }

            Vector3 zoom = new Vector3(0, 0, (prevMouseState.ScrollWheelValue - 
                mouseState.ScrollWheelValue) * (speed / 500));

            Matrix cameraViewRotationMatrix = Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw);

            forward = Vector3.Transform(Vector3.Forward, cameraViewRotationMatrix);

            position += Vector3.Transform(moveVector, Matrix.CreateRotationY(yaw));
            position += Vector3.Transform(zoom, cameraViewRotationMatrix);

            target = forward + position;

            prevMouseState = mouseState;
        }

        public void UpdateProjection()
        {
            float aspectRatio = EngineGlobals.GraphicsDevice.Viewport.AspectRatio;

            projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4, aspectRatio, NearPlane, FarPlane);
        }
    }
}
